package org.hawk.gwt.ppc.reflect.lang;

import java.util.HashMap;
import java.util.Map;

/**
 * Replacement for the class loaders of java language.
 * This class main goal is to provide {@link ClassDelegate} 
 * a way to load the classes from GWT modules.
 * 
 * @author alex.bereznevatiy@gmail.com
 */
public abstract class ClassLoaderDelegate {
	private Map<String,ClassDelegate<?>> classes;
	private Map<String,Class<?>> notReflectable;
	/**
	 * Method to implement in entry points of each reflected module. 
	 * @return list of classes to be available for this
	 * class loader.
	 */
	protected abstract ClassDelegate<?>[] getClasses();
	/**
	 * This method used to perform higher compile-time 
	 * precalculation/optimization. Since usually all classes
	 * of specific module are located either under the same package 
	 * or have common parent package its easy to determine wherever 
	 * this class loader can load the class with specific name by 
	 * checking does the package of the class is a subpackage of 
	 * this one.
	 * @return parent package of all classes available 
	 * for this class loader. General contract is not 
	 * to return <code>null</code> here. Use empty string  
	 * if you don't what to use this kind of optimization.
	 */
	protected abstract String getPackage();
	/**
	 * This method used to create and load the classes that are 
	 * not intended to be reflected but should serve as runtime
	 * types for e.g. to check parameters or return type. 
	 * @return list of classes available for this class loader but 
	 * not able to be reflected with {@link ClassDelegate}
	 */
	protected abstract Class<?>[] getNotReflectableClasses();
	/**
	 * Find the class by the name passed. If class cannot be found - 
	 * ClassNotFoundException will be raised. This implementation 
	 * differs from default java implementation since it searches 
	 * the classes only within the current class loader.
	 * @param name
	 * @return class with name specified (never <code>null</code>)
	 * @throws ClassNotFoundException
	 */
	public ClassDelegate<?> loadClass(String name) throws ClassNotFoundException {
		if(name==null||name.length()==0||!name.startsWith(getPackage())){
			throw new ClassNotFoundException(name);
		}
		if(classes==null){
			preloadClasses();
		}
		if(classes.get(name)==null){
			throw new ClassNotFoundException(name);
		}
		ClassDelegate<?> cls = classes.get(name);
		if(!cls.isLoaded()){
			cls.load(this);
		}
		return cls;
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected ClassDelegate<?> loadNotReflectableClass(String name) {
		if(notReflectable==null){
			preloadNotReflectable();
		}
		Class<?> cls = notReflectable.get(name);
		if(cls==null){
			return null;
		}
		ClassDelegate<?> rez = new ClassDelegate(cls);
		rez.load(this);
		return rez;
	}
	
	private void preloadNotReflectable(){
		notReflectable = new HashMap<String, Class<?>>();
		for(Class<?> cls : getNotReflectableClasses()){
			notReflectable.put(cls.getName(), cls);
		}
	}
	
	private void preloadClasses() {
		classes = new HashMap<String, ClassDelegate<?>>();
		for(ClassDelegate<?> cls : getClasses()){
			classes.put(cls.getName(), cls);
		}
	}
	/**
	 * @return parent class loader. For this implementation
	 * it is always system loader (or null if it is already system one).
	 */
	public final ClassLoaderDelegate getParent() {
		if(this==SystemClassLoader.getInstance()){
			return null;
		}
		return SystemClassLoader.getInstance();
	}
}
